package com.wmx.jdbc_template_app.controller;

import com.wmx.jdbc_template_app.helper.ApplicationContextHelper;
import com.wmx.jdbc_template_app.helper.BeanFactoryHelper;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * DataSourceTransactionManager 数据源事务管理 + 获取 IOC 容器(BeanFactory & ApplicationContext)
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2022/4/21 20:47
 */
@RestController
public class PhoneController2 {

    /**
     * Spring 默认已经提供了 IOC 容器(BeanFactory & ApplicationContext) 的实例，可以直接注入使用。
     * 注意：从容器里面获取自己是没有的，但是可以直接注入使用。
     */
    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private BeanFactory beanFactory;

    /**
     * JdbcTemplate 是 core 包的核心类，用于简化 JDBC 操作，还能避免一些常见的错误，如忘记关闭数据库连接
     * Spring Boot 默认提供了数据源，默认提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中会自己注入数据源，使用起来也不用再自己来关闭数据库连接
     */
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 当程序中只有一个数据源(javax.sql.DataSource)时，则 Spring 默认会将其初始化并添加到容器中，直接获取即可使用.
     * 当程序中有多个数据源，但是设置了首选 Bean 时(比如@Primary标识)，则需要手动初始化实例。
     * 当程序中有多个数据源，且没有设置首选 Bean 时，则必须手动初始化实例。
     */
    @Autowired
    private DataSourceTransactionManager transactionManager;

    /**
     * DataSourceTransactionManager 数据源事务管理
     * http://localhost:8080/transaction/manager?path=xxx
     *
     * @param path ：通过参数来手动制造异常，不传入时，后台就会报空指针异常，然后看事务是否回滚
     */
    @GetMapping("transaction/manager")
    public String transactionManager(String path) {
        String msg = "数据插入成功！";
        // 定义事务属性
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        // 设置事务传播行为，REQUIRED：如果当前存在事务，则加入该事务，如果当前不存在事务，则创建一个新的事务。required（必须的）
        // 默认就是 PROPAGATION_REQUIRED，不显示设置也没有关系，这里只是介绍使用方式。
        transactionDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
        // 获取事务状态
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            // 添加数据
            addData("苹果S");
            // 由参数决定是否空指针异常
            System.out.println(path.length());
            // 添加数据
            addData("华为P");
            // 提交事务
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(transactionStatus);
            msg = "异常回滚：" + e.getClass().toString();
            e.printStackTrace();
        }
        return msg;
    }

    /**
     * 数据库操作
     *
     * @param brand ：品牌名称
     */
    private void addData(String brand) {
        String sql = "INSERT INTO iphone(id,name,price,publish_time) VALUES (?,?,?,?)";
        Object[] objects = new Object[4];
        objects[0] = null;
        objects[1] = brand + String.valueOf(System.currentTimeMillis()).substring(10, 12);
        objects[2] = 900 + new SecureRandom().nextFloat() * 5000;
        objects[3] = new Date();
        jdbcTemplate.update(sql, objects);
    }

    /**
     * IOC 容器(BeanFactory & ApplicationContext) 实例直接注入演示
     * http://localhost:8080/application/context
     *
     * @return
     */
    @GetMapping("application/context")
    @SuppressWarnings("Duplicates")
    public Map<String, Object> applicationContext() {
        PhoneController phoneController1 = applicationContext.getBean(PhoneController.class);
        PhoneController phoneController2 = (PhoneController) beanFactory.getBean("phoneController");

        List<Map<String, Object>> mapList1 = phoneController1.userList();
        List<Map<String, Object>> mapList2 = phoneController2.userList();

        Map<String, Object> dataMap = new HashMap<>(4);
        dataMap.put("mapList1", mapList1);
        dataMap.put("mapList2", mapList2);
        return dataMap;
    }

    /**
     * http://localhost:8080/application/context2
     * IOC 容器(BeanFactory & ApplicationContext) 实例通过内置 WebApplicationContextUtils 工具类获取
     * <p>
     * 获取 web 应用的根 WebApplicationContext(ApplicationContext的子接口)
     * * WebApplicationContext getRequiredWebApplicationContext(ServletContext sc)
     * * WebApplicationContext getWebApplicationContext(ServletContext sc)
     * * WebApplicationContext findWebApplicationContext(ServletContext sc)
     *
     * @return
     */
    @GetMapping("application/context2")
    public Map<String, Object> applicationContext2(HttpServletRequest request) {
        ApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());

        PhoneController phoneController2 = (PhoneController) webApplicationContext.getBean("phoneController");
        List<Map<String, Object>> mapList2 = phoneController2.userList();
        Map<String, Object> dataMap = new HashMap<>(4);
        dataMap.put("mapList2", mapList2);
        return dataMap;
    }

    /**
     * BeanFactoryAware + BeanFactory 获取 BeanFactory
     * http://localhost:8080/application/context3
     *
     * @return
     */
    @GetMapping("application/context3")
    @SuppressWarnings("Duplicates")
    public Map<String, Object> applicationContext3() {
        PhoneController phoneController = BeanFactoryHelper.getBean(PhoneController.class);
        List<Map<String, Object>> mapList = phoneController.userList();
        Map<String, Object> dataMap = new HashMap<>(4);
        dataMap.put("mapList3", mapList);
        return dataMap;
    }

    /**
     * ApplicationContextAware + ApplicationContext 获取 ApplicationContext
     * http://localhost:8080/application/context4
     *
     * @return
     */
    @GetMapping("application/context4")
    @SuppressWarnings("Duplicates")
    public Map<String, Object> applicationContext4() {
        PhoneController phoneController = ApplicationContextHelper.getBean(PhoneController.class);
        List<Map<String, Object>> mapList = phoneController.userList();
        Map<String, Object> dataMap = new HashMap<>(4);
        dataMap.put("mapList4", mapList);
        return dataMap;
    }
}
